#include "Parser.hpp"
using namespace ym;
ym::Parser::Parser()
{
    memset(jsonText, 0, sizeof(jsonText));
    currentChar = nullptr;
    jsonEnd = nullptr;
    nextElement = false;
}
ym::Parser::~Parser()
{
    currentChar = nullptr;
    jsonEnd = nullptr;
#ifdef DEBUG
    std::cout << "Parser: free\n";
#endif
}
Json *Parser::nextJson()
{
    skipWhiteSpace();
    if (isEnd())
        return new Json();
    if (*currentChar == '-' || *currentChar == '+' || (*currentChar >= '0' && *currentChar <= '9'))
    {
        return parseNumber();
    }
    try
    {
        switch (*currentChar)
        {
        case 'n':
            return parseNull();
        case 't':
        case 'f':
            return parseBool();
        case '"':
            return parseString();
        case '[':
            return parseArray();
        case '{':
            return parseObject();
        default:
            char buff[0xff];
            sprintf_s(buff, 0xff, "Unknown type -%c- code:%d", *currentChar, (int)*currentChar);
            throw std::runtime_error(buff);
        }
    }
    catch (std::runtime_error &e)
    {
        throw e;
    }
}

bool ym::Parser::findNextChar(const char ch)
{
    skipWhiteSpace();
    return *currentChar == ch;
}

Json *Parser::load(const char *str)
{
    int len = strlen(str);
    strcpy_s(jsonText, sizeof(jsonText), str);
    currentChar = jsonText;
    jsonEnd = jsonText + len;
    Json *json = nextJson();
    skipWhiteSpace();
    if (!isEnd())
        throw std::runtime_error("Parser:\tend error");
    return json;
}

Json *Parser::loadFromFile(const char *filename)
{
    std::ifstream input(filename, std::ios::in);
    if (!input.is_open())
    {
        throw std::runtime_error("Parser:\tnot found the file");
    }
    char buff[READFILE_BUFFSIZE] = {0};
    memset(buff, 0, sizeof(buff));
    char *p = buff;
    while (!input.eof())
    {
        input >> (*p++);
        if (p - buff >= READFILE_BUFFSIZE)
        {
            break;
        }
    }
    input.close();
#ifdef DEBUG
    std::cout << "Parser:\tfile-content:\n"
              << buff << std::endl;
#endif
    return load(buff);
}

void Parser::skipWhiteSpace()
{
    while (!isEnd())
    {
        if (*currentChar == ' ' || *currentChar == '\n' || *currentChar == '\t' || *currentChar == '\b' || *currentChar == '\r')
        {
            currentChar++;
            continue;
        }
        break;
    }
}

bool ym::Parser::isEnd()
{
    return currentChar >= jsonEnd;
}

Json *ym::Parser::parseNull()
{
    if (!strncmp(currentChar, JSONTEXT_NULL, strlen(JSONTEXT_NULL)))
    {
        currentChar += strlen(JSONTEXT_NULL);
        return new Json();
    }

    throw std::runtime_error("Parser:\tParse null error");
}

Json *Parser::parseBool()
{
    if (!strncmp(currentChar, JSONTEXT_TRUE, strlen(JSONTEXT_TRUE)))
    {
        currentChar += strlen(JSONTEXT_TRUE);
        return new Json(true);
    }
    else if (!strncmp(currentChar, JSONTEXT_FALSE, strlen(JSONTEXT_FALSE)))
    {

        currentChar += strlen(JSONTEXT_FALSE);
        return new Json(false);
    }

    throw std::runtime_error("Parser:\tParse bool error");
}

Json *Parser::parseNumber()
{
    bool f = false;
    std::string num;
    while (!isEnd())
    {
        if (*currentChar == ' ' || *currentChar == ',' || *currentChar == '\n' || *currentChar == ']' || *currentChar == '}')
        {
            break;
        }
        if (*currentChar == '.')
            f = true;
        num += *currentChar++;
    }
    if (f)
    {
        return new Json(atof(num.c_str()));
    }
    else
    {
        return new Json(atoi(num.c_str()));
    }
}

Json *Parser::parseString()
{
    std::string str;
    currentChar++;
    while (!isEnd())
    {
        if (*currentChar == '\\')
        {
            str += *(currentChar + 1);
            currentChar += 2;
            continue;
        }
        if (*currentChar == '"')
        {
            break;
        }
        str += *currentChar++;
    }
    currentChar++;
    return new Json(str);
}

Json *Parser::parseArray()
{
    Json::Array *array = new Json::Array();
    currentChar++;
    skipWhiteSpace();
    nextElement = false;
    while (!isEnd() && *currentChar != ']')
    {
        try
        {
            Json *json = nextJson();
            array->push_back(json);
            nextElement = false;
            skipWhiteSpace();
            if (*currentChar == ',')
            {
                nextElement = true;
                currentChar++;
                skipWhiteSpace();
            }
        }
        catch (std::runtime_error &e)
        {
            delete array;
            throw e;
        }
    }
    if (nextElement)
    {
        delete array;
        throw std::runtime_error("Array:\telements are missing after ','");
    }
    if (*currentChar != ']')
    {
        delete array;
        throw std::runtime_error("Array:\tmissing an end marker");
    }
    currentChar++;
    return new Json(array);
}

Json *Parser::parseObject()
{
    Json::Object *object = new Json::Object();
    currentChar++;
    skipWhiteSpace();
    nextElement = false;
    while (!isEnd() && *currentChar != '}')
    {
        try
        {
            if (*currentChar != '\"')
            {
                throw std::runtime_error("Object:\tmissing key");
            }
            Json *key = parseString();
            if (!findNextChar(':'))
            {
                throw std::runtime_error(std::string("Object:\tmissing ':' after ").append(key->getString()));
            }
            currentChar++;
            Json *json = nextJson();
            object->emplace(key->getString(), json);
            delete key;
            nextElement = false;
            skipWhiteSpace();
            if (*currentChar == ',')
            {
                nextElement = true;
                currentChar++;
                skipWhiteSpace();
            }
        }
        catch (std::runtime_error &e)
        {
            delete object;
            throw e;
        }
    }
    if (nextElement)
    {
        throw std::runtime_error("Object:\tmissing an end marker");
    }
    if (*currentChar != '}')
    {
        delete object;
        throw std::runtime_error("Object:\tmissing an end marker");
    }
    currentChar++;
    return new Json(object);
}
